package za.org.jcicada.comms.impl;

import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import za.org.jcicada.comms.IKatcpSerializerStrategy;
import za.org.jcicada.comms.messages.CommsMessage;
import za.org.jcicada.genss.PhysicalURN;
import za.org.jcicada.genss.ServiceURN;
import za.org.jcicada.genss.SystemURN;
import za.org.jcicada.genss.alarm.AlarmURN;
import za.org.jcicada.genss.messages.Result;
import za.org.jcicada.genss.messages.SuccessCode;
import za.org.jcicada.genss.sensor.SensorURN;
import za.org.jcicada.katcp.Message;

public class KatcpSerializerStrategy implements IKatcpSerializerStrategy {

    /** Logger handle */
    private static final Log logger = LogFactory.getLog(KatcpSerializerStrategy.class);
    
    private Object marshal;
    private Object unmarshal;
    
    public KatcpSerializerStrategy(Object marshal, Object unmarshal) {
        this.marshal = marshal;
        this.unmarshal = unmarshal;
    }
    
    @Override
    public CommsMessage fromKatcp(Message msg) throws IOException {
        String methodName = msg.getName();
        @SuppressWarnings("rawtypes")
		Class[] params = { String[].class };
        try {
            Method m = unmarshal.getClass().getMethod(methodName, params);
            logger.debug(String.format("Dispatching call to message handler: %s", methodName));
            return (CommsMessage)m.invoke(unmarshal, (Object) msg.getArguments());
        } catch (NoSuchMethodException e) {
        	throw new IOException(String.format("Can't get method %s(%s)", methodName, params[0]), e);
        } catch (Exception ex) {
            throw new IOException("Could not convert KATCP Message to CommsMessage", ex);
        }
    }

    @Override
	public Message toKatcp(CommsMessage arg0) throws IOException {
        String[] composition = arg0.getClass().getCanonicalName().split("\\.");
        String methodName = composition[composition.length - 1];
        @SuppressWarnings("rawtypes")
		Class[] params = { arg0.getClass() };
		try {
	        Method m = marshal.getClass().getMethod(methodName, params);
	        logger.debug(String.format("Dispatching call to message handler: %s", methodName));
	        Object args = m.invoke(marshal, arg0);
	        List<String> stringArgs = new ArrayList<String>();
	        for (Object object : (Object[]) args) {
	        	stringArgs.add(object == null? "null" : object.toString());
	        }
	        return createMessage(methodName, stringArgs.toArray(new String[0]));
		} catch (NoSuchMethodException e) {
			throw new IOException(String.format("Can't get method %s(%s)", methodName, params[0]), e);
		} catch (Exception e) {
			throw new IOException("Could not convert CommsMessage to KATCP message", e);
		}
    }
    
    private Message createMessage(String name, String[] args) {
        Message msg;
        if (name.endsWith("Inform")) {
            msg = new Message(Message.Type.INFORM, name, args);
        } else if (name.endsWith("Reply")) {
            msg = new Message(Message.Type.REPLY, name, args);
        } else {
            msg = new Message(Message.Type.REQUEST, name, args);
        }
        return msg;
    }
    
	public static final Object[] concatenate(final Object[] first, final Object[] second) {
		// Assuming non-null for brevity.
		final ArrayList<Object> resultList = new ArrayList<Object>(Arrays.asList(first));
		resultList.addAll(new ArrayList<Object>(Arrays.asList(second)));
		return resultList.toArray(new Object[resultList.size()]);
	}
	
	public static final Set<SystemURN> getSetSystemURNs(String[] arg, int from) {
		Set<SystemURN> systemURNs = new HashSet<SystemURN>();
		for (int i = from; i < arg.length; i++) {
			systemURNs.add(parseSystemURN(arg, i));
		}
		return systemURNs;
	}

	public static final Set<SensorURN> getSetSensorURNs(String[] arg, int from) {
		Set<SensorURN> sensorURNs = new HashSet<SensorURN>();
		for (int i = from; i < arg.length; i++) {
			sensorURNs.add(parseSensorURN(arg, i));
		}
		return sensorURNs;
	}

	public static final Set<ServiceURN> getSetServiceURNs(String[] arg, int from) {
		Set<ServiceURN> serviceURNs = new HashSet<ServiceURN>();
		for (int i = from; i < arg.length; i++) {
			serviceURNs.add(parseServiceURN(arg, i));
		}
		return serviceURNs;
	}
	
	public static final Object[] getCollectionArray(Collection<?> collection) {
		if (collection == null) {
			return new Object[] { null };
		} else {
			return collection.toArray(new Object[collection.size()]);
		}
	}
	
	public static final Result parseResult(String[] args) {
		return new Result(SuccessCode.valueOf(args[0]), args[1]);
	}
	
	public static final URI parseURI(String[] args, int index) {
		try {
			return new URI(args[index]);
		} catch (Exception ex) {
			return null;
		}
	}
	
	public static final ServiceURN parseServiceURN(String[] args, int index) {
		try {
			return new ServiceURN(args[index]);
		} catch (Exception ex) {
			return null;
		}
	}
	
	public static final SystemURN parseSystemURN(String[] args, int index) {
		try {
			return new SystemURN(args[index]);
		} catch (Exception ex) {
			return null;
		}
	}
	
	public static final PhysicalURN parsePhysicalURN(String[] args, int index) {
		try {
			return new PhysicalURN(args[index]);
		} catch (Exception ex) {
			return null;
		}
	}
	
	public static final SensorURN parseSensorURN(String[] args, int index) {
		try {
			return new SensorURN(args[index]);
		} catch (Exception ex) {
			return null;
		}
	}
	
	public static final AlarmURN parseAlarmURN(String[] args, int index) {
		try {
			return new AlarmURN(args[index]);
		} catch (Exception ex) {
			return null;
		}
	}

}
